package com.example.observer.eventbus;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ObserverRegistry {
    
    private Map<Class<?>, Set<ObserverAction>> registry = new HashMap<>();

    public void register(Object observer) {
        for (Map.Entry<Class<?>, Set<ObserverAction>> entry : getAllActions(observer).entrySet()) {
            Class<?> eventType = entry.getKey();
            Set<ObserverAction> eventTypeActions = registry.get(eventType);
            if (eventTypeActions == null) {
                registry.put(eventType, new HashSet<ObserverAction>());
                eventTypeActions = registry.get(eventType);
            }
            eventTypeActions.addAll(entry.getValue());
        }
    }

    public Set<ObserverAction> getMacthedObserverActions(Object eventObject) {
        HashSet<ObserverAction> actions = new HashSet<>();
        Class<?> eventType = eventObject.getClass();
        for (Map.Entry<Class<?>, Set<ObserverAction>> entry : registry.entrySet()) {
            if (eventType.isAssignableFrom(entry.getKey())) {
                actions.addAll(entry.getValue());
            }
        }
        return actions;
    }

    private Map<Class<?>, Set<ObserverAction>> getAllActions(Object observer) {
        ArrayList<Method> methods = getAllAnnotatedMethods(observer);
        HashMap<Class<?>, Set<ObserverAction>> map = new HashMap<>();
        for (Method method : methods) {
            Class<?> eventType = method.getParameterTypes()[0];
            Set<ObserverAction> eventTypeActions = map.get(eventType);
            if (eventTypeActions == null) {
                map.put(eventType, new HashSet<ObserverAction>());
                eventTypeActions = map.get(eventType);
            }

            ObserverAction action = new ObserverAction(method, observer);
            eventTypeActions.add(action);
        }
        return map;
    }

    private ArrayList<Method> getAllAnnotatedMethods(Object observer) {
        ArrayList<Method> methods = new ArrayList<>();  
        for (Method method : observer.getClass().getDeclaredMethods()) {
            if (method.isAnnotationPresent(Subscribe.class)
            && method.getParameterCount() == 1) {
                methods.add(method);
            }
        }
        return methods;
    }

}
